package com.ecode.annotation.process;

import com.ecode.annotation.ErrorCode;
import com.ecode.annotation.core.DefaultErrorCoderBuilder;
import com.ecode.annotation.core.ErrorCodeBuilder;
import com.ecode.annotation.model.ErrorCodeDO;
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.ecode.annotation.ErrorCode")
@AutoService(Processor.class)
public class ErrorCodeProcessor extends AbstractProcessor {

    private Messager messager; // 编译时期输入日志的
    private JavacTrees javacTrees; // 提供了待处理的抽象语法树
    private TreeMaker treeMaker; // 封装了创建AST节点的一些方法
    private Names names; // 提供了创建标识符的方法
    private ErrorCode errorCode;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.javacTrees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ErrorCode.class);
        elementsAnnotatedWith.forEach(e -> {
            ErrorCode errorCode = e.getAnnotation(ErrorCode.class);
            JCTree tree = javacTrees.getTree(e);
            tree.accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    for (JCTree jcTree : jcClassDecl.defs) {
                        if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) {

                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;
                            if(jcVariableDecl.name.toString().equals("DEFAULT_ERROR_CODE")){
                                ErrorCodeBuilder builder =  new DefaultErrorCoderBuilder();
                                Set<ErrorCodeDO> errorCodeDOS = builder.parseErrorCode(errorCode);
                                errorCodeDOS.forEach(errorCodeDO -> {
                                    jcClassDecl.defs = jcClassDecl.defs.prepend(makeVariableDecl(errorCodeDO, jcClassDecl, jcVariableDecl));
                                });
                            }
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }
                    super.visitClassDef(jcClassDecl);
                }
            });
        });
        return true;
    }

    public JCTree.JCVariableDecl makeVariableDecl(ErrorCodeDO errorCodeDO, JCTree.JCClassDecl jcClassDecl, JCTree.JCVariableDecl jcVariableDecl ){
        JCTree.JCVariableDecl var = (JCTree.JCVariableDecl) jcVariableDecl.clone();
        var.name = names.fromString(errorCodeDO.buildCode());

        JCTree.JCExpression paramCode = treeMaker.Literal(errorCodeDO.buildCode());
        JCTree.JCExpression paramMsg = treeMaker.Literal(errorCodeDO.getMsg());
        List<JCTree.JCExpression> params = List.of(paramCode, paramMsg);
        JCTree.JCNewClass newItem = treeMaker.NewClass(
                null,
                List.nil(),
                treeMaker.Ident(names.fromString(jcClassDecl.name.toString())),
                params,
                null
        );
        var.init = newItem;
        return var;
    }
}